import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;





/****
 * 
 * 
 * This class Allow the EasyAwarness to Communicate Via Bluetooth.
 * You can:
 * 	- Discover device and service associated
 * 	- Connect to a specific device with a specific service
 * 
 * When a connection is established, this connection is given to a ConnectionDialer.
 * If you want to use the first mode (discover device and service associated) you have to provide the ConnectionDialer associated
 * to a service before running the research 
 * 
 * 
 * @author Mathieu Maret
 */


public class BluetoothInput  extends EasyAwarenessInput implements DiscoveryListener{
	private LocalDevice local;
	private DiscoveryAgent agent = null;
	private Vector devicesFound = null;
	private ServiceRecord[] servicesFound = null;
	private static Object lock=new Object();

	/**
	 * @param inputType commun name of the input
	 * 
	 * Create a Bluetooth input with a commun name and associated with the type EasyAwarenessInput.BLUETOOTH;
	 * 
	 */
	public BluetoothInput(String inputType){
		type = EasyAwarenessInput.BLUETOOTH;
		devicesFound = new Vector();
		ConnectionDialerForService = new Hashtable();
		name = inputType;
		try {
			local = LocalDevice.getLocalDevice();
		}catch (BluetoothStateException bse) {
			bse.printStackTrace();
		}
		agent = local.getDiscoveryAgent();
	}


	/**
	 * 
	 * Add a ConnectionDialer associated to a service in the Bluetooth input.
	 * The bluetooth service is identified with his UUID  
	 * 
	 * @param id UUID of the service
	 * @param dialer Connection dialer associated to the service
	 */
	public void addConnectionDialer( UUID id,ConnectionDialer dialer){
		ConnectionDialerForService.put(id, dialer);
	}


	/**
	 * Run a new research for devices around.
	 * When the research if finished correctly, a service research will be run on each devices found
	 * 
	 * The devices discovered could be access by the method getDevicesFound()
	 * 
	 * A Message of Type Message.ERROR could be generated if the discover failed.
	 * The content of this message could be :
	 * 			- Inquiry error
	 *			- Inquiry terminated
	 * depending on the Bluetooth error. See the bluetooth API for "inquiryCompleted" for more details
	 */
	public void deviceResearch() {

		devicesFound = new Vector();
		try {
			if(!agent.startInquiry(DiscoveryAgent.GIAC,this)) {
				stopResearch();
			}
		}catch(BluetoothStateException bse) {
			bse.printStackTrace();
		}

	}
	
	public void stopResearch(){
		agent.cancelInquiry(this);
	}


	/**
	 * Run a research for service on all the device found in getDevicesFound()
	 * When a service is found, the BluetoothInput will try to connect to it if
	 * a ConnectionDialer is associated to a service of this device 
	 *
	 *If a Error occured, a Message will be generated with the type Message.ERROR
	 *with the content :
	 *	-SERVICE_SEARCH_DEVICE_NOT_REACHABLE
	 *	-SERVICE_SEARCH_ERROR
	 *	-SERVICE_SEARCH_NO_RECORDS
	 *	-SERVICE_SEARCH_TERMINATED
	 *depending on the bluetooth error. See DiscoveryListener In Bluetooth API for more details
	 */
	public void serviceResearch(){

		for(int i= 0; i< devicesFound.size();i++){
			RemoteDevice device = (RemoteDevice)devicesFound.elementAt(i);
			if(device.getBluetoothAddress() != local.getBluetoothAddress()){
				int[] attributes = {0x0003}; 
				//for(Enumeration keys = ConnectionDialerForService.keys();keys.hasMoreElements();){
				//	keys.nextElement();
				//	length ++;
				//}
				UUID[] uuids = new UUID[1];

				for(Enumeration keys = ConnectionDialerForService.keys();keys.hasMoreElements();){
					UUID id = (UUID)keys.nextElement();
					uuids[0] = id;
					//elementNumber ++;

//					// Can get a Null pointeur exeption (at least on the wirelss toolkit) 
					// when searching for a bluetooth service on a device without service
					int error;  
					try {
						error = 0;
						agent.searchServices(attributes,uuids,device,this);
						
						if(error == 0){
							try {
								synchronized(lock){
									lock.wait();
								}
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}




					} catch (BluetoothStateException e) {
						// Error handling code here
						e.printStackTrace();
					}
					catch(NullPointerException e){
						error = 1;

					}
				}


			}

		}

	}


	public void deviceDiscovered(RemoteDevice btDevice, DeviceClass arg1) {
		// same device may found several times during single search
		if (devicesFound.indexOf(btDevice) == -1) {
			devicesFound.addElement(btDevice);
		}
	}


	public void inquiryCompleted(int inquiry) {
		switch (inquiry) {
		case DiscoveryListener.INQUIRY_COMPLETED:
			this.serviceResearch();
			break;

		case DiscoveryListener.INQUIRY_ERROR:
			Message m = new Message("Inquiry error");
			m.setType(Message.ERROR);
			this.getManager().messageReceive(m);
			break;
		case DiscoveryListener.INQUIRY_TERMINATED:
			Message m2 = new Message("Inquiry terminated");
			m2.setType(Message.ERROR);
			this.getManager().messageReceive(m2);
		default:

			break;
		} 

	}


	public void serviceSearchCompleted(int arg0, int respCode) {
		switch(respCode) {
		case DiscoveryListener.SERVICE_SEARCH_COMPLETED:
			for (int i = 0; i < servicesFound.length ; i ++){
				ServiceRecord rec = servicesFound[i];
				if(rec == null){
					Message mNull = new Message("rec null \n");
					this.getManager().messageReceive(mNull);
				}
				if(rec.getAttributeValue(0x0003)== null){

					Message m1= new Message("ERROR : Cannot get the UUID from the server  \n");
					m1.setType(Message.ERROR);
					this.getManager().messageReceive(m1);
				}else{
					UUID id = (UUID)(rec.getAttributeValue(0x0003).getValue());
					if(ConnectionDialerForService.containsKey(id)){
						String connectionURL = rec.getConnectionURL(
								ServiceRecord.NOAUTHENTICATE_NOENCRYPT,
								false);
						/*
							StreamConnection conn= null;
							Message m;

							try {
								conn =  (StreamConnection) Connector.open(connectionURL);
							} catch (IOException e) {

								e.printStackTrace();
							}

							ConnectionDialer dial = (ConnectionDialer)ConnectionDialerForService.get((UUID)rec.getAttributeValue(0x0003).getValue());
							m = dial.deal(conn);
							m.setConnectionURL((Object)connectionURL);
							m.setSourceType(type);
							dial.close();

							//m.setContent("Connection Dialer found"+m.getContent());
							this.getManager().messageReceive(m);
						 */
						ConnectionDialer dial = (ConnectionDialer)ConnectionDialerForService.get((UUID)rec.getAttributeValue(0x0003).getValue());

						runConnection runner = new runConnection(connectionURL, dial,rec, this.getManager());
						new Thread(runner).start();

					}else{

						Message m = new Message("");
						m.setContent("no dialer found for service :"+id+"\n" );
						m.setType(Message.DEBUG);
						this.getManager().messageReceive(m);

					}



				}
			}

			synchronized(lock){
				lock.notify();
			}


			break;
		case DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE:
			Message mNotReachable = new Message("SERVICE_SEARCH_DEVICE_NOT_REACHABLE");
			mNotReachable.setType(Message.ERROR);
			this.getManager().messageReceive(mNotReachable);
			synchronized(lock){
				lock.notify();
			}


			break;
		case DiscoveryListener.SERVICE_SEARCH_ERROR:
			Message mError = new Message("SERVICE_SEARCH_ERROR");
			mError.setType(Message.ERROR);
			this.getManager().messageReceive(mError);

			synchronized(lock){
				lock.notify();
			}

			break;
		case DiscoveryListener.SERVICE_SEARCH_NO_RECORDS:
			Message mNoRecord = new Message("SERVICE_SEARCH_NO_RECORDS");
			mNoRecord.setType(Message.ERROR);
			this.getManager().messageReceive(mNoRecord);
			synchronized(lock){
				lock.notify();
			}


			break;
		case DiscoveryListener.SERVICE_SEARCH_TERMINATED:
			Message mTerminated = new Message("SERVICE_SEARCH_TERMINATED");
			mTerminated.setType(Message.ERROR);
			this.getManager().messageReceive(mTerminated);
			synchronized(lock){
				lock.notify();
			}


			break;
		}

	}


	public void servicesDiscovered(int arg0, ServiceRecord[] arg1) {
		servicesFound = arg1;


	}


	/**
	 * Allow you to connect directly to a servie on a specific device.
	 * The connection will be given to the ConnectionDialer when the connection will be established
	 * 
	 * @param bluetoothUrl the bluetooth URL of the device you want to connect
	 * @param idService UUID of the service you want to connect
	 * @param dial The ConnectionDialer which will receive the established connection
	 */
	public void connecteTo(String bluetoothUrl,UUID idService, ConnectionDialer dial){

		for(int i= 0; i< devicesFound.size();i++){
			RemoteDevice device = (RemoteDevice)devicesFound.elementAt(i);
			if(device.getBluetoothAddress().equals(bluetoothUrl)){
				int[] attributes = {0x0003}; 
				UUID[] uuids = new UUID[1];
				uuids[0] = idService;

				int error;  
				try {
					error = 0;
					agent.searchServices(attributes,uuids,device,new connecteServiceListener(idService,dial));
					if(error == 0){
						try {
							synchronized(lock){
								lock.wait();
							}
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}


				} catch (BluetoothStateException e) {
					e.printStackTrace();
				}
				catch(NullPointerException e){
					error = 1;

				}
			}





		}


	}
	public class connecteServiceListener  implements DiscoveryListener{
		UUID _id;
		ServiceRecord ServiceFound[];
		ConnectionDialer dial;
		public connecteServiceListener(UUID id,ConnectionDialer dial){
			_id = id;

			this.dial = dial;
		}

		public void deviceDiscovered(RemoteDevice arg0, DeviceClass arg1) {

		}

		public void inquiryCompleted(int arg0) {

		}

		public void serviceSearchCompleted(int arg0, int respCode) {
			switch(respCode) {
			case DiscoveryListener.SERVICE_SEARCH_COMPLETED:
				for (int i = 0; i < servicesFound.length ; i ++){
					ServiceRecord rec = servicesFound[i];
					if(rec == null){
						Message mNull = new Message("rec null \n");
						mNull.setType(Message.ERROR);
						manager.messageReceive(mNull);
					}
					if(rec.getAttributeValue(0x0003)== null){

						Message m1= new Message("ERROR : Cannot get the UUID from the server  \n");
						m1.setType(Message.ERROR);
						manager.messageReceive(m1);
					}else{
						UUID id = (UUID)(rec.getAttributeValue(0x0003).getValue());
						if(_id.equals(id)){
							String connectionURL = rec.getConnectionURL(
									ServiceRecord.NOAUTHENTICATE_NOENCRYPT,
									false);
							runConnection runner = new runConnection(connectionURL, dial,rec, manager);
							new Thread(runner).start();

						}else{

						}



					}
				}

				synchronized(lock){
					lock.notify();
				}


				break;
			case DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE:
				Message mNotReachable = new Message("DEBUG :SERVICE_SEARCH_DEVICE_NOT_REACHABLE");
				mNotReachable.setType(Message.ERROR);
				manager.messageReceive(mNotReachable);
				synchronized(lock){
					lock.notify();
				}


				break;
			case DiscoveryListener.SERVICE_SEARCH_ERROR:
				Message mError = new Message("DEBUG :SERVICE_SEARCH_ERROR");
				mError.setType(Message.ERROR);
				manager.messageReceive(mError);

				synchronized(lock){
					lock.notify();
				}

				break;
			case DiscoveryListener.SERVICE_SEARCH_NO_RECORDS:
				Message mNoRecord = new Message("DEBUG :SERVICE_SEARCH_NO_RECORDS");
				mNoRecord.setType(Message.ERROR);
				manager.messageReceive(mNoRecord);
				synchronized(lock){
					lock.notify();
				}


				break;
			case DiscoveryListener.SERVICE_SEARCH_TERMINATED:
				Message mTerminated = new Message("DEBUG :SERVICE_SEARCH_TERMINATED");
				mTerminated.setType(Message.ERROR);
				manager.messageReceive(mTerminated);
				synchronized(lock){
					lock.notify();
				}


				break;
			}
		}

		public void servicesDiscovered(int arg0, ServiceRecord[] arg1) {
			servicesFound = arg1;
		}

	}



	public class runConnection implements Runnable{
		private String ConnectionURL;
		StreamConnection conn;
		ConnectionDialer dial;
		EasyAwarenessManager manager;
		ServiceRecord rec;

		public runConnection(String url, ConnectionDialer d, ServiceRecord rec, EasyAwarenessManager manag){
			ConnectionURL = url;
			dial = d;
			manager = manag;
			this.rec = rec;
		}
		public void run() {
			Message m;

			try {
				conn =  (StreamConnection) Connector.open(ConnectionURL);
			} catch (IOException e) {

				e.printStackTrace();
			}

			m = dial.deal(conn);
			m.setConnectionURL((Object)ConnectionURL);
			m.setSourceType(type);
			m.setHost(rec.getHostDevice().getBluetoothAddress());

			dial.close();
			manager.messageReceive(m);
		}
	}



	public void Research() {
		this.deviceResearch();

	}


	public Vector getDevicesFound() {
		return devicesFound;
	}


	public void setDevicesFound(Vector devicesFound) {
		this.devicesFound = devicesFound;
	}


}
